Blending Problems

FROM - Operations Research: Applications and Algorithms 4th Edition, p80, Wayne L. Winston

Sunco Oil manufactures three types of gasoline (gas 1, gas 2, gas 3). Each type is produced by blending three types of crude oil (crude 1, crude 2, crude 3). The sales prices per barrel of gasoline are as follows. Sunco can purchase up to 5000 barrels of each type of crude oil.

The three types of gasoline differ in their octane rating and sulfur content. The crude oil blends to form gas 1 must have an average octane rating of at least 10 and contain at most 1% sulfur. The crude oil blended to form gas 2 must have an average octane rating of at least 8 and contain at most 2% sulfur. The crude oil blended to form gas 3 must have an octane rating of at least 6 and contain at most 1% sulfur. The octane rating and the sulfur content of the three types of oil are. It costs $4 to transform one barrel of gasoline, and Sunco's refinery can produce up to 14,000 barrels of gasoline daily.

Sunco’s customers require the following amounts of each gasoline: gas 1- 3000 barrels/day, gas 2- 2000 barrels/day, and gas 3- 1000 barrels/day. The company considers it an obligation to meet these demands. Sunco also has the option of advertising to stimulate demand for its products. Each dollar spent daily in advertising a particular type of gas increases the daily demand for that type of gas by 10 barrels. For example, if Sunco decides to spend $20 daily in advertising gas 2, the daily demand for gas 2 will increase by 20(10) = 200 barrels. Formulate an LP that will enable Sunco to maximize daily profits (profits= revenue – costs).

Gas Sales Price per Barrel Crude Purchase Price per Barrel
1 70 1 45
2 60 2 34
3 50 3 25
Crude Octane Rating Sulfur Content(%)
1 12 0.5
2 6 2.0
3 7 3.0

In [1]:
from gurobipy import *


gas_salses  = [70, 60, 50]
crude_purchases  = [45, 34, 25]

gas_constrations = [3000, 2000, 1000]
crude_constrations  = 5000


crude_octane_raing = [12, 6, 7]


octane_level_constraints = [
    [2, -4, -2],
    [0, 4, -2],
    [6, 0, 2]
]

# C11	-0.005X11+0.01X21+0.02X31<=0
# C12	-0.015X12+0.01X32<=0
# C13	-0.005X13+0.01X23+0.02X33<=0



x_variables_names = [ ["x"+str(i)+str(j) for i in range(3)] for j in range(3)]
ad_variables_names = ["a"+str(i) for i in range(3)]

In [74]:
# Model
m = Model("project_selection")

x_variables = []
ad_variables = []

for i in range(3):
    x_variables.append([])
    for j in range(3):
        x_variables[i].append(
            m.addVar(
            vtype=GRB.CONTINUOUS,
            obj = gas_salses[j] - crude_purchases[i] - 4,
            name="(%s)" % (x_variables_names[i][j])))

for j in range(3):
    ad_variables.append(
            m.addVar(
            vtype=GRB.CONTINUOUS,
            obj = -1,
            name="(%s)" % (ad_variables_names[j])))

m.modelSense = GRB.MAXIMIZE
m.update()

m.getObjective()


Out[74]:
<gurobi.LinExpr: 21.0 (x00) + 11.0 (x10) + (x20) + 32.0 (x01) + 22.0 (x11) + 12.0 (x21) + 41.0 (x02) + 31.0 (x12) + 21.0 (x22) + -1.0 (a0) + -1.0 (a1) + -1.0 (a2)>

In [75]:
# Gas Constrains
for gas in range(3):
    constraions = [x_variables[crude][gas] for crude in range(3)]
    constraions.append(-10 * ad_variables[gas])
    m.addConstr(
        quicksum(constraions
            ) == gas_constrations[gas],
        "Gas %d requirment " % gas)
m.update()
m.getConstrs()


Out[75]:
[<gurobi.Constr Gas 0 requirment >,
 <gurobi.Constr Gas 1 requirment >,
 <gurobi.Constr Gas 2 requirment >]

In [76]:
for crude in range(3):
    constraions = [x_variables[crude][gas] for gas in range(3)]
    m.addConstr(
        quicksum(constraions
            ) <= crude_constrations,
        "Crude Oil %d requirment " % crude)
m.update()
m.getConstrs()

In [78]:
# Constraint for refinery capacity limit:
constraions = [x_variables[crude][gas] for crude in range(3) for gas in range(3)]
m.addConstr(
    quicksum(constraions) == 14000, "reginery capacity limit " )

m.update()
m.getConstrs()


Out[78]:
[<gurobi.Constr Gas 0 requirment >,
 <gurobi.Constr Gas 1 requirment >,
 <gurobi.Constr Gas 2 requirment >,
 <gurobi.Constr Crude Oil 0 requirment >,
 <gurobi.Constr Crude Oil 1 requirment >,
 <gurobi.Constr Crude Oil 2 requirment >,
 <gurobi.Constr reginery capacity limit >]

In [79]:
for crude in range(3):
    constrains = [octane_level_constraints[crude][gas]*x_variables[crude][gas] for gas in range(3)]
    m.addConstr(
        quicksum(constraions
            ) >= 0,
        "Octane level in Gas  %d requirment " % crude)
    
m.update()
m.getConstrs()


Out[79]:
[<gurobi.Constr Gas 0 requirment >,
 <gurobi.Constr Gas 1 requirment >,
 <gurobi.Constr Gas 2 requirment >,
 <gurobi.Constr Crude Oil 0 requirment >,
 <gurobi.Constr Crude Oil 1 requirment >,
 <gurobi.Constr Crude Oil 2 requirment >,
 <gurobi.Constr reginery capacity limit >,
 <gurobi.Constr Octane level in Gas  0 requirment >,
 <gurobi.Constr Octane level in Gas  1 requirment >,
 <gurobi.Constr Octane level in Gas  2 requirment >]

In [80]:
sulfur_content = [0.005, 0.02, 0.03]
sulfur_at_most = [0.01, 0.02, 0.01]

for gas in range(3):
    constrains_lhs = [sulfur_content[crude]*x_variables[crude][gas] for crude in range(3)]
#     print(constrains_lhs)
    constrains_rhs =[sulfur_at_most[crude]*x_variables[crude][gas] for crude in range(3)]
#     print(constrains_rhs)
    m.addConstr(
        quicksum(constrains_lhs) >= quicksum(constrains_rhs),
        "Sulfur content in Gas  %d requirment " % gas)
    
    
m.update()
m.getConstrs()
m.optimize()


Optimize a model with 13 rows, 12 columns and 63 nonzeros
Coefficient statistics:
  Matrix range     [5e-03, 1e+01]
  Objective range  [1e+00, 4e+01]
  Bounds range     [0e+00, 0e+00]
  RHS range        [1e+03, 1e+04]
Presolve removed 6 rows and 3 columns
Presolve time: 0.01s
Presolved: 7 rows, 9 columns, 27 nonzeros

Iteration    Objective       Primal Inf.    Dual Inf.      Time
       0    5.3320000e+05   5.625000e+02   0.000000e+00      0s
       4    4.0820000e+05   0.000000e+00   0.000000e+00      0s

Solved in 4 iterations and 0.03 seconds
Optimal objective  4.082000000e+05

In [84]:
for v in m.getVars():
    print (v.varName, v.x)

    
print (m.getObjective().getValue())


(x00) 4000.0
(x10) 0.0
(x20) 0.0
(x01) 2000.0
(x11) 2000.0
(x21) 1000.0
(x02) 5000.0
(x12) 0.0
(x22) 0.0
(a0) 800.0
(a1) 0.0
(a2) 0.0
408200.0

In [ ]: